/*
 * Copyright (c) 2005 Psiqueware. All Rights Reserved.
 */

package com.psiqueware.alheli.dao.hibernate;

import com.psiqueware.alheli.basico.BaseObjectInterface;
import com.psiqueware.alheli.exception.NullArgumentException;
import com.psiqueware.alheli.exception.NullPostConditionException;
import com.psiqueware.alheli.logging.Logger;
import com.psiqueware.alheli.logging.LoggerFactory;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.RootClass;
import org.w3c.dom.Document;

import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * Nos permite establecer program&aacute;ticamente las propiedades de hibernate.
 *
 * @noinspection DuplicateStringLiteralInspection, ClassNamePrefixedWithPackageName
 */

public final class HibernateServiceLocator
{

    private static final ThreadLocal<Session> threadSession = new ThreadLocal<Session>();

    /**
     * Logger de clase para HibernateServiceLocator
     */
    private static final Logger s_log = LoggerFactory.getLogger(HibernateServiceLocator.class);

    static
    {   //Inicializamos la f�brica de sesiones.
        try
        {
            rebuildSessionFactory();
        }
        catch (Exception e)
        {
            final String error = "<error><msg>No se pudo configurar autom&aacute;ticamente la f&aacute;brica de sesiones de hibernate.</msg></error>";
            s_log.error(error);
            s_log.error(e);
            if (null != e.getCause())
            {
                s_log.error(e.getCause());
            }
        }

    }

    private static Configuration configuration = null;

    private static SessionFactory sessionFactory;

    /**
     * Construye una HibernateServiceLocator.
     * <p>No requiere argumentos.</p>
     * <b>Esta clase debe utilizarse por medio de sus m&eacute;todos est&aacute;ticos.</b>
     */
    public HibernateServiceLocator()
    {
    }


    /**
     * Obtiene la {@link Session} actual. En caso de no existir crea una.
     *
     * @return {@link Session} actual.
     *
     * @throws org.hibernate.HibernateException en caso de un error al crear una {@link Session}.
     * @noinspection HibernateResourceOpenedButNotSafelyClosed
     */
    public static Session currentSession() throws HibernateException
    {
        Session session = threadSession.get();
        if ((session == null || !session.isOpen()) && isSessionFactoryCreated())
        {
            session = sessionFactory.openSession();
            if (s_log.isDebugEnabled()) s_log.debug("Se cre&oacute; una nueva Session: " + session);
            threadSession.set(session);
        }
        if (null == session)
            throw new NullPostConditionException("<error>A fallado una postcondici&oacute;n. El argumento de salida 'session' no puede ser NULL.</error>");

        return session;
    }

    /**
     * Nos dice si la f&aacute;brica de {@link SessionFactory} se encuentra inicializada.
     *
     * @return boolean <code>true</code> si lo esta <code>false</code> de otra manera.
     */
    public static boolean isSessionFactoryCreated()
    {
        return null != sessionFactory;
    }

    /**
     * Cierra la sesi&oacute;n
     *
     * @param session a cerrar.
     */
    public static void closeSession(final Session session) throws HibernateException
    {
        threadSession.set(null);
        if (session != null && !session.isDirty())
        {
            session.flush();
            session.disconnect();
            session.close();
        }
    }

    /**
     * Rebuild the SessionFactory with the static Configuration.
     */
    public static void rebuildSessionFactory()
    {
        ClassLoader classLoader = HibernateServiceLocator.class.getClassLoader();
        URL url = classLoader.getResource("hibernate.cfg.xml");
        rebuildSessionFactory(url);
    }

    /**
     * Rebuild the SessionFactory with the static Configuration.
     */
    public static void rebuildSessionFactory(URL url)
    {
        try
        {
            s_log.debug("hibernate url servicelocator: " + url);
            if (null == url) throw new NullArgumentException("<error>El argumento 'url' no puede ser NULL.</error>");
            configuration = new Configuration();
            configuration.configure(url);


            //Establecemos si nos estamos conectando por ThinDriver o DataSource
            if (s_log.isDebugEnabled())
            {
                String property = configuration.getProperty("connection.datasource");
                if (property != null)
                {
                    s_log.debug("Decoracion datasource = " + property);
                }
                else
                {
                    s_log.debug("La Aplicaci&oacute;n se conecta por ThinDriver a la URL: " + configuration.getProperty("connection.url"));
                }
            }

            // Create the SessionFactory
            rebuildSessionFactory(configuration);
        }
        catch (Exception e)
        {
            final String error = "<error><msg>No se pudo realizar la configuraci&oacute;n de Hibernate Cl&aacute;sico.</msg><parametros>" + url + "</parametros></error>";
            s_log.error(error);
            s_log.error(e);
            throw new HibernateServiceException(error, e);
        }
    }

    /**
     * Rebuild the SessionFactory with the static Configuration.
     */
    public static void rebuildSessionFactory(Document document)
    {
        try
        {
            s_log.debug("hibernate document servicelocator: " + document);
            if (null == document)
                throw new NullArgumentException("<error>El argumento 'document' no puede ser NULL.</error>");
            configuration = new Configuration();
            configuration.configure(document);


            //Establecemos si nos estamos conectando por ThinDriver o DataSource
            if (s_log.isInfoEnabled())
            {
                String property = configuration.getProperty("connection.datasource");
                if (property != null)
                {
                    s_log.info("Decoracion datasource = " + property);
                }
                else
                {
                    s_log.info("La Aplicaci&oacute;n se conecta por ThinDriver a la URL: " + configuration.getProperty("connection.url"));
                }
            }

            // Create the SessionFactory
            rebuildSessionFactory(configuration);
        }
        catch (Exception e)
        {
            final String error = "<error><msg>No se pudo realizar la configuraci&oacute;n de Hibernate Cl&aacute;sico.</msg><parametros>" + document + "</parametros></error>";
            s_log.error(error);
            s_log.error(e);
            throw new HibernateServiceException(error, e);
        }
    }

    /**
     * Rebuild the SessionFactory with the given hibernate Configuration.
     */
    public static synchronized void rebuildSessionFactory(Configuration cfg)
    {
        try
        {
            // Create the SessionFactory
            sessionFactory = cfg.buildSessionFactory();
            s_log.debug("Creado el sessionFactory");
        }
        catch (HibernateException he)
        {
            throw new HibernateException("Configuration problem: " + he.getMessage(), he);
        }
    }

    public static List<Class<? extends BaseObjectInterface>> listaObjetosPersistibles()
    {
        String className = null;
        try
        {
            final List<Class<? extends BaseObjectInterface>> objects = new ArrayList<Class<? extends BaseObjectInterface>>();
            final Iterator classMappings = configuration.getClassMappings();
            while (classMappings.hasNext())
            {
                final Object next = classMappings.next();
                final PersistentClass persistentClass = (PersistentClass) next;
                className = persistentClass.getClassName();
                if (RootClass.class.isInstance(persistentClass))
                {
                    objects.add((Class<? extends BaseObjectInterface>) Class.forName(className));
                }
            }
            return objects;
        }
        catch (ClassNotFoundException e)
        {
            final String error = "<error><msg>No se encontr&oacute; la clase configurada en Hibernate.</msg><parametros>" + className + "</parametros></error>";
            s_log.error(error);
            s_log.error(e);
            throw new RuntimeException(error, e);
        }
    }

    /**
     * Retrieves the a new Session.
     *
     * @return a new Session
     */
    public static Session getSession()
    {
        return sessionFactory.openSession();
    }


    /**
     * Reconnects a hibernate Session to the current Thread.
     */
    public static void reconnect(Session session)
    {
        threadSession.set(session);
    }

    /**
     * Disconnect and return Session from current Thread.
     */
    public static Session disconnectSession()
    {

        Session session = getSession();
        try
        {
            threadSession.set(null);
            if (session.isConnected() && session.isOpen())
            {
                session.disconnect();
            }
        }
        catch (HibernateException ex)
        {
            s_log.error(ex);
        }
        return session;
    }

    /**
     * Returns the original hibernate configuration.
     */
    public static Configuration getConfiguration()
    {
        return configuration;
    }

}
